﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using System.IO;

namespace algorithms.algorithms.network.gossip {

    public class ShortestPathFlood {
        private int time;
        private int maxTime;

        private Dictionary<int, GossipPathNode> gossipPathNodes = new Dictionary<int, GossipPathNode>();

        public ShortestPathFlood() {

            time = 0;
            maxTime = 20;

            // when the tape has finished playing, run the algorithm.            
            NetworkModel.Instance.TapeEnded += RunAlgorithm;
        }

        private void Init() {

            foreach (BaseNode b in NetworkModel.Instance.Nodes.Values.ToList()) {
                GossipPathNode newNode = new GossipPathNode(b);
                gossipPathNodes.Add(b.ID, newNode);
            }

            foreach (GossipPathNode gn in gossipPathNodes.Values.ToList()) {
                gn.MakeNeighbourhood(gossipPathNodes);

                gn.InitialPaths();
            }
        }

        public void RunAlgorithm() {

            // Initialise - set up all gossip nodes, and calculate initial variance.
            Init();

            for (int i = 0; i < maxTime; i++) {
                Console.WriteLine("time is " + i);
                foreach (GossipPathNode node in gossipPathNodes.Values.ToList()) {
                    // ALGORITHM

                    if (node.ToBroadcast.Count() != 0) {

                        // flood broadcast all paths that should be "sent on"
                        foreach (GossipPathNode g in node.Neighbourhood) {
                            g.UpdateState(node.ToBroadcast);
                        }

                        node.resetBroadcast();
                    }
                }

                foreach (GossipPathNode node in gossipPathNodes.Values.ToList()) {
                    node.updateBroadcast();
                }

                time++;
            }

            using (StreamWriter output = new StreamWriter("shortestpaths.csv")) {
                foreach (GossipPathNode g in gossipPathNodes.Values.ToList()) {
                    foreach (HashSet<ShortestPath> h in g.ShortestPaths.Values.ToList()) {
                        foreach (ShortestPath sp in h.ToList()) {
                            foreach (int g2 in sp.Path) {
                                output.Write(gossipPathNodes[g2].Name + ",");
                            }
                            output.WriteLine("");
                        }
                    }
                }
            }

        }
    }

    internal class GossipPathNode : SimpleNode {

        // this holds the number of shortest paths that this node appears on. Currently unused.
        private int shortestPathCount;
        public int ShortestPathCount {
            get {
                return shortestPathCount;
            }
        }

        // contains shortest paths from this node to all other nodes that can be reached
        private Dictionary<int, HashSet<ShortestPath>> shortestPaths;
        public Dictionary<int, HashSet<ShortestPath>> ShortestPaths {
            get {
                return shortestPaths;
            }
        }

        // contains all the neighbours of this node
        // this is computed from getting everyone from the end of every out edge
        private HashSet<GossipPathNode> neighbourhood;
        public HashSet<GossipPathNode> Neighbourhood {
            get {
                return neighbourhood;
            }
        }

        // contains all information received within a round regarding shortest paths from a target
        private Dictionary<int, HashSet<ShortestPath>> toProcess;
        public Dictionary<int, HashSet<ShortestPath>> ToProcess {
            get {
                return toProcess;
            }
        }

        // contains all shortest paths that need to be broadcast to neighbours
        private Dictionary<int, HashSet<ShortestPath>> toBroadcast;
        public Dictionary<int, HashSet<ShortestPath>> ToBroadcast {
            get {
                return toBroadcast;
            }
        }
        
        public void MakeNeighbourhood(Dictionary<int, GossipPathNode> allNodes) {
            foreach (Edge e in NetworkModel.Instance.Nodes[this.ID].OutEdges.ToList()) {
                neighbourhood.Add(allNodes[e.Target.ID]);
            }

            foreach (Edge e in NetworkModel.Instance.Nodes[this.ID].InEdges.ToList()) {
                neighbourhood.Add(allNodes[e.Source.ID]);
            }
        }

        public void InitialPaths() {

            foreach (GossipPathNode g in neighbourhood.ToList()) {

                ShortestPath sp = new ShortestPath();

                // ADD IN THE NODES
                sp.addToPath(this.ID);
                sp.addToPath(g.ID);

                HashSet<ShortestPath> temp = new HashSet<ShortestPath>();
                temp.Add(sp);

                shortestPaths.Add(g.ID, temp);
                toBroadcast.Add(g.ID, temp);

            }

        }

        public void UpdateState(Dictionary<int, HashSet<ShortestPath>> newPaths) {

            foreach (KeyValuePair<int, HashSet<ShortestPath>> paths in newPaths.ToList()) {

                // if this is a shortest path to me, skip.
                if (paths.Key == ID) { continue; }


                // if I do not already have a shortest path for this target (from previous ROUNDS)
                if (!shortestPaths.ContainsKey(paths.Key)) {

                    // if I have not received any information about these shortest paths from anyone else
                    if (!toProcess.ContainsKey(paths.Key)) {

                        // add the target and the shortest paths for later computation
                        toProcess.Add(paths.Key, paths.Value);
                    } else {

                        // else, I have received other shortest paths to this target
                        // so add new shortest paths to the current processing list for this target
                        // NOTE: received this round, so by definition: same length.
                        foreach (ShortestPath sp in paths.Value) {

                            toProcess[paths.Key].Add(sp);
                        }
                    }
                }

                // NOTE: if a shortest path is already held from a previous round, the shortest path just
                // received must be, by definition, longer than the current shortest path which took less time to arrive

            }
        }

        public void resetBroadcast() {
            // make new toBroadcast list for this round
            toBroadcast = new Dictionary<int, HashSet<ShortestPath>>();
        }


        public void updateBroadcast() {

            // for all new paths in to process:
            foreach (KeyValuePair<int, HashSet<ShortestPath>> newpaths in toProcess.ToList()) {

                HashSet<ShortestPath> temp = new HashSet<ShortestPath>();

                foreach (ShortestPath sp in newpaths.Value.ToList()) {

                    ShortestPath sp2 = new ShortestPath();
                    sp2.addToPath(this.ID);
                    foreach (int g in sp.Path) {
                        sp2.addToPath(g);
                    }

                    temp.Add(sp2);
                }

                // add these paths to the current record of shortest paths
                shortestPaths.Add(newpaths.Key, temp);

                // add these paths to the new broadcast list for next round
                toBroadcast.Add(newpaths.Key, temp);
            }

            // make fresh dictionary for new information received this round
            toProcess = new Dictionary<int, HashSet<ShortestPath>>();
        }

        public GossipPathNode(BaseNode referenceNode)
            : base(referenceNode.ID, referenceNode.Name) {

            neighbourhood = new HashSet<GossipPathNode>();
            toBroadcast = new Dictionary<int, HashSet<ShortestPath>>();
            toProcess = new Dictionary<int, HashSet<ShortestPath>>();
            shortestPaths = new Dictionary<int, HashSet<ShortestPath>>();
            shortestPathCount = 0;
        }
    }

    internal class ShortestPath {

        private int source;
        public int Source {
            get {
                return source;
            }
        }

        private int target;
        public int Target {
            get {
                return target;
            }
        }

        private List<int> path;
        public List<int> Path {
            get {
                return path;
            }
        }

        public void addToPath(int g) {

            if (path.Count == 0) {
                source = g;
            } else {
                target = g;
            }
            path.Add(g);
        }

        public ShortestPath() {
            path = new List<int>();
        }
    }
}
